/* s^2 - HTTP requests
   Copyright (C) 2018 Ariadne Devos

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#ifndef _sHT_WEB_STATE_H
#define _sHT_WEB_STATE_H

#include <stddef.h>
#include <sys/uio.h>

/* Tip: curl can dump whole HTTP connections. I prefer reverse-engineering that first,
   then correct incompatibilities with the specification.

   HTTP/2: curl --trace - https://en.wikipedia.org/,
   HTTP/1.1: curl --trace - --http1.1 https://tools.ietf.org/rfcmarkup?doc=2616,
   HTTP/1.0: curl --trace - --http1.0 https://www.rfc-editor.org/rfc/rfc1945.txt */

/** This lookup-free, constant-time perfect hash function has been found with
  brute-force:

  for mod in range(8, 16):
    for i in range(4):
      comb0 = set()
      red0 = set()
      for e in elems:
        c0 = len(e)
        if i >= c0:
          c1 = 20
        else:
          c1 = e[i].encode()[0]
        comb0.add(c0 + c1)
        red0.add((3 * c0 + c1) % mod)
        if len(red0) >= 8:
          print (mod, i, comb0, red0)

  This returned (12, 1, ...) and (15, 1, ...). Choose the former. */
#define _s2_http_method_hash_fun(len, second) ((3 * (len) + (second)) % 12)

#define _s2_http_method_hash_expect(expect, len, second) \
  ((expect) / (_s2_http_method_hash_fun(len, second) == (expect)))

enum s2_http_method
{
	S2_HTTP_METHOD_UNKNOWN0 = 0,
	S2_HTTP_METHOD_TRACE = _s2_http_method_hash_expect(1, 5, (int) 'R'),
	S2_HTTP_METHOD_UNKNOWN1 = 2,
	S2_HTTP_METHOD_DELETE = _s2_http_method_hash_expect(3, 6, (int) 'E'),
	S2_HTTP_METHOD_CONNECT = _s2_http_method_hash_expect(4, 7, (int) 'O'),
	S2_HTTP_METHOD_OPTIONS = _s2_http_method_hash_expect(5, 7, (int) 'P'),
	S2_HTTP_METHOD_GET = _s2_http_method_hash_expect(6, 3, (int) 'E'),
	S2_HTTP_METHOD_POST = _s2_http_method_hash_expect(7, 4, (int) 'O'),
	S2_HTTP_METHOD_UNKNOWN2 = 8,
	S2_HTTP_METHOD_HEAD = _s2_http_method_hash_expect(9, 4, (int) 'E'),
	S2_HTTP_METHOD_PUT = _s2_http_method_hash_expect(10, 3, (int) 'U'),
	S2_HTTP_METHOD_UNKNOWN3 = 11,
#define S2_HTTP_METHOD_MAX 11
};
#define S2_HTTP_METHOD_MIN_LENGTH 3
#define S2_HTTP_METHOD_MAX_LENGTH 7

#define S2_HTTP_METHOD_UNKNOWNS \
	((1 << S2_HTTP_METHOD_UNKNOWN0) | (1 << S2_HTTP_METHOD_UNKNOWN1) \
	 | (1 << S2_HTTP_METHOD_UNKNOWN2) | (1 << S2_HTTP_METHOD_UNKNOWN3))

/** Test if @var{method} doesn't represent an actual method.

  I.e., it is one of S2_HTTP_METHOD_UNKNOWN<N>.
  This does not have to be wrapped in a @var{sHT_test_hidden}. */
__attribute__((const))
static inline _Bool
s2_http_method_unknown(enum s2_http_method method)
{
	return sHT_test_hidden(method, (1 << method) & S2_HTTP_METHOD_UNKNOWNS);
}

/** Hash a method name, followed by a space, and compute its length

  @var{bytes}: eight initialised bytes, aligned on an 8-byte boundary

  If the method name is not followed by a space, or it is not supported,
  an S2_HTTP_METHOD<N> may be returned, for some N, and the length may
  be meaningless.

  An tiny identifier for the method is returned; the hash is perfect, but
  not minimal. The identifier and the length may speculatively be incorrect,
  but they are always within bounds.

  This is a constant function. */
__attribute__((const))
struct s2_http_method_parsed { enum s2_http_method method; unsigned length; }
s2_http_method_parse_fast(char bytes[8]);

enum s2_http_version
{
	/* HTTP/2 */
	S2_HTTP_VERSION_HTTP_2,
	/* HTTP/1.1 */
	S2_HTTP_VERSION_HTTP_1_1,
	/* HTTP/1.0 */
	S2_HTTP_VERSION_HTTP_1_0,
#if 0
	/* Specified by Tim-Berners Lee */
	S2_HTTP_VERSION_HTTP_0_1,
#endif
};

#define S2_HTTP_VERSION_MAX_LENGTH 8

/* Tip: Firefox > Inspect Element > Network > Headers > Request headers */
enum s2_http_request_header
{
	/* E.g., en.wikipedia.org */
	S2_HOST,
	/* E.g., curl/7.52.1 */
	S2_USER_AGENT,
	/* E.g., text/html,application/xhtml+xml,application/xml;q=0.9,STAR/STAR;q=0.8 */
	S2_ACCEPT,
	/* E.g., gzip, deflate, br */
	S2_ACCEPT_ENCODING,
	/* E.g., en-US,en;q=0.5 */
	S2_ACCEPT_LANGUAGE,
	/* Another URI */
	S2_REFERER,
	/* E.g., a=b; c=d*/
	S2_COOKIE,
	/* E.g., keep-alive */
	S2_CONNECTION,
};

/* Tip: Firefox > Inspect Element > Network > Headers > Response headers */
enum s2_http_response_header
{
	/* E.g., max-age=86400 */
	S2_HTTP_RESPONSE_CACHE_CONTROL,
	/* E.g., Keep-Alive */
	S2_HTTP_RESPONSE_CONNECTION,
	/* E.g. br (brotli, also possible: gzip) */
	S2_HTTP_RESPONSE_CONTENT_ENCODING,
	/* E.g., text/html; charset=utf-8 */
	S2_HTTP_RESPONSE_CONTENT_TYPE,
	/* E.g., Date: Sat, 05 Jan 2019 12:30:50 GMT */
	S2_HTTP_RESPONSE_DATE,
	/* E.g., timeout=5, max=100 */
	S2_HTTP_RESPONSE_KEEP_ALIVE,
	/* E.g., Server: Apache/2.2.22 (Debian) */
	S2_HTTP_RESPONSE_SERVER,
	/* E.g., max-age=3600 */
	S2_HTTP_RESPONSE_STRICT_TRANSPORT_SECURITY,
	/* E.g., chunked */
	S2_HTTP_RESPONSE_TRANSFER_ENCODING,
	/* E.g., Accept-Encoding */
	S2_HTTP_RESPONSE_VARY,
	/* E.g., GNU Terry Pratchett, or someone else */
	S2_HTTP_RESPONSE_X_CLACKS_OVERHEAD,
#define S2_HTTP_RESPONSE_N (S2_HTTP_RESPONSE_TRANSFER_ENCODING + 1)
};

struct sHT_io_fork;
struct sHT_io_fork
{
	/* Continue parsing/emitting, reading/writing if necessary. */
	void (* thirsty)(struct sHT_io_fork *self, struct iovec *latest);
	/* Keep the connection open, but don't parse/emit anything from or to it. */
	void (* pause)(struct sHT_io_fork *self, struct iovec *latest);
	/* Close an end of the connection, after flushing */
	void (* done)(struct sHT_io_fork *self, struct iovec *latest);
};

struct s2_http_io
{

	/* CONNECT, OPTIONS are seven bytes long, the eight byte
	   is a space (0x20). The space must be there. */
	union {
		char http_version[S2_HTTP_VERSION_MAX_LENGTH + 1];
		char method[S2_HTTP_METHOD_MAX_LENGTH + 1];
		/* The Referer or Cookie attribute is probably among the longest.
		   Referer: https://www.ietf.org/mail-archive/web/ietf-announce/current/msg15716.html
		   Referer: https://ipfs.io/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco/Wikipedia.html
		   (length: 92)
		   256: conservative
		   +2: \r\n */
		char header_line[254 + 2];
	} parsing_accumulate;

	/** Parse data read from the connection, specified by @var{parse}.

	  @var{parse}: a read-only buffer that can be incremented, with an
	    associated read-write length. These are not acccessed
	    concurrently outside this function.

	  Tail calls into @var{out_nextparse}. */
	void (* in_parse)(struct s2_http_io *self, struct iovec *parse);

	/** Specify data to write to the connection in @var{emit}

	   @var{emit}: a write-only buffer that can be enlarged, up to
	     @var{max}, with an associated, read-write length that is not
	     necessarily zero. These are not accessed concurrently outside
	     this function
	   @var{max}: the upper bound on the length of @var{emit}

	  Tail calls into @var{out_nextemit}. */
	int (* in_emit)(struct s2_http_io *self, struct iovec *emit, size_t max);

	struct io_fork *out_nextparse;
	struct io_fork *out_nextemit;
};

#endif
